package com.digitalxpert.jargle.itext.rtf;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Properties;
import java.util.TreeSet;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import com.lowagie.text.Chunk;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.ElementTags;
import com.lowagie.text.PageSize;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Rectangle;
import com.lowagie.text.TextElementArray;
import com.lowagie.text.html.Markup;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfPageEventHelper;
import com.lowagie.text.pdf.PdfTemplate;
import com.lowagie.text.pdf.PdfWriter;
import com.lowagie.text.xml.SAXmyHandler;
import com.lowagie.text.xml.TagMap;
import com.lowagie.text.xml.XmlPeer;

public class Test {

    /**
     * This is an example of a PageEvents class you should write.
     * This is an inner class to keep all the code of the example in one file.
     * If you want to use a PageEvent, you may want to put the code in a separate class.
     */

    class MyPageEvents extends PdfPageEventHelper {

            /** we will keep a list of speakers */
            TreeSet speakers = new TreeSet();

            /** This is the contentbyte object of the writer */
            PdfContentByte cb;

            /** we will put the final number of pages in a template */
            PdfTemplate template;

            /** this is the BaseFont we are going to use for the header / footer */
            BaseFont bf = null;

            /** this is the current act of the play */
            String act = "";
            
            /**
             * Every speaker will be tagged, so that he can be added to the list of speakers.
             * @see com.lowagie.text.pdf.PdfPageEventHelper#onGenericTag(com.lowagie.text.pdf.PdfWriter, com.lowagie.text.Document, com.lowagie.text.Rectangle, java.lang.String)
             */
            public void onGenericTag(PdfWriter writer, Document document,
                            Rectangle rect, String text) {
                    speakers.add(new Speaker(text));
            }

            /**
             * The first thing to do when the document is opened, is to define the BaseFont,
             * get the Direct Content object and create the template that will hold the final
             * number of pages.
             * @see com.lowagie.text.pdf.PdfPageEventHelper#onOpenDocument(com.lowagie.text.pdf.PdfWriter, com.lowagie.text.Document)
             */
            public void onOpenDocument(PdfWriter writer, Document document) {
                    try {
                            bf = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.CP1252,
                                            BaseFont.NOT_EMBEDDED);
                            cb = writer.getDirectContent();
                            template = cb.createTemplate(50, 50);
                    } catch (DocumentException de) {
                    } catch (IOException ioe) {
                    }
            }

            /**
             * Every ACT is seen as a Chapter. We get the title of the act, so that
             * we can display it in the header.
             * @see com.lowagie.text.pdf.PdfPageEventHelper#onChapter(com.lowagie.text.pdf.PdfWriter, com.lowagie.text.Document, float, com.lowagie.text.Paragraph)
             */
            public void onChapter(PdfWriter writer, Document document,
                            float paragraphPosition, Paragraph title) {
                    StringBuffer buf = new StringBuffer();
                    for (Iterator i = title.getChunks().iterator(); i.hasNext();) {
                            Chunk chunk = (Chunk) i.next();
                            buf.append(chunk.getContent());
                    }
                    act = buf.toString();
            }

            /**
             * After the content of the page is written, we put page X of Y
             * at the bottom of the page and we add either "Romeo and Juliet"
             * of the title of the current act as a header.
             * @see com.lowagie.text.pdf.PdfPageEventHelper#onEndPage(com.lowagie.text.pdf.PdfWriter, com.lowagie.text.Document)
             */
            public void onEndPage(PdfWriter writer, Document document) {
                    int pageN = writer.getPageNumber();
                    String text = "Page " + pageN + " of ";
                    float len = bf.getWidthPoint(text, 8);
                    cb.beginText();
                    cb.setFontAndSize(bf, 8);
                    cb.setTextMatrix(280, 30);
                    cb.showText(text);
                    cb.endText();
                    cb.addTemplate(template, 280 + len, 30);
                    cb.beginText();
                    cb.setFontAndSize(bf, 8);
                    cb.setTextMatrix(280, 820);
                    if (pageN % 2 == 1) {
                            cb.showText("Romeo and Juliet");
                    } else {
                            cb.showText(act);
                    }
                    cb.endText();
            }

            /**
             * Just before the document is closed, we add the final number of pages to
             * the template.
             * @see com.lowagie.text.pdf.PdfPageEventHelper#onCloseDocument(com.lowagie.text.pdf.PdfWriter, com.lowagie.text.Document)
             */
            public void onCloseDocument(PdfWriter writer, Document document) {
                    template.beginText();
                    template.setFontAndSize(bf, 8);
                    template.showText(String.valueOf(writer.getPageNumber() - 1));
                    template.endText();
            }

            /**
             * Getting the list of speakers.
             * @return      a list of speakers and the number of occurances.
             */
            public TreeSet getSpeakers() {
                    return speakers;
            }
    }

    /**
     * Gets a PageEvents object.
     * @return a new PageEvents object
     */
    public MyPageEvents getPageEvents() {
            return new MyPageEvents();
    }
    /**
     * Gets a Handler object.
     * @param document  the document on which the handler operates
     * @return a Handler object
     */
    public MyHandler getXmlHandler(Document document) {
            try {
                    return new MyHandler(document, new RomeoJulietMap());
            } catch (IOException e) {
                    e.printStackTrace();
            }
            return null;
    }
    
    /**
     * Converts a play in XML into PDF.
     * @param args no arguments needed
     */
    public static void main(String[] args) {

            System.out.println("Romeo and Juliet");

            // step 1: creation of a document-object
            Document document = new Document(PageSize.A4, 80, 50, 30, 65);

            try {
                    // step 2:
                    // we create a writer that listens to the document
                    // and directs a XML-stream to a file
                    PdfWriter writer = PdfWriter.getInstance(document,
                                    new FileOutputStream("RomeoJuliet.pdf"));

                    // create add the event handler
                    MyPageEvents events = new Test().getPageEvents();
                    writer.setPageEvent(events);

                    // step 3: we create a parser and set the document handler
                    SAXParser parser = SAXParserFactory.newInstance().newSAXParser();

                    // step 4: we parse the document
                    parser.parse("playRomeoJuliet.xml", new Test().getXmlHandler(document));

                    document.newPage();
                    Speaker speaker;
                    for (Iterator i = events.getSpeakers().iterator(); i.hasNext();) {
                            speaker = (Speaker) i.next();
                            document.add(new Paragraph(speaker.getName() + ": "
                                            + speaker.getOccurrance() + " speech blocks"));
                    }
                    document.close();

            } catch (Exception e) {
                    e.printStackTrace();
                    System.err.println(e.getMessage());
            }
    }

    /**
     * Special implementation of het XML handler.
     * It adds a paragraph after each SPEAKER block and
     * avoids closing the document after the final closing tag.
     */
    class MyHandler extends SAXmyHandler {

            /**
             * We have to override the constructor
             * @param document      the Document object
             * @param tagmap        the tagmap
             */
            public MyHandler(Document document, HashMap tagmap) {
                    super(document, tagmap);
            }

            /**
             * We only alter the handling of some endtags.
             * @param uri   the uri of the namespace
             * @param lname the local name of the tag
             * @param name  the name of the tag
             */
            public void endElement(String uri, String lname, String name) {
                    if (myTags.containsKey(name)) {
                            XmlPeer peer = (XmlPeer) myTags.get(name);
                            // we don't want the document to be close
                            // because we are going to add a page after the xml is parsed
                            if (isDocumentRoot(peer.getTag())) {
                                    return;
                            }
                            handleEndingTags(peer.getTag());
                            // we want to add a paragraph after the speaker chunk
                            if ("SPEAKER".equals(name)) {
                                    try {
                                            TextElementArray previous = (TextElementArray) stack
                                                            .pop();
                                            previous.add(new Paragraph(16));
                                            stack.push(previous);
                                    } catch (EmptyStackException ese) {
                                    }
                            }
                    } else {
                            handleEndingTags(name);
                    }
            }
            public void startElement(String uri, String lname, String name, Attributes attrs) {
                if (myTags.containsKey(name)) {
                    XmlPeer peer = (XmlPeer) myTags.get(name);
                    handleStartingTags(peer.getTag(), peer.getAttributes(attrs));
                }
                else {
                    Properties attributes = new Properties();
                    if (attrs != null) {
                        for (int i = 0; i < attrs.getLength(); i++) {
                            String attribute = attrs.getQName(i);
                            attributes.setProperty(attribute, attrs.getValue(i));
                        }
                    }
                    handleStartingTags(name, attributes);
                }
            }
    }

    /**
     * Normally you either choose to use a HashMap with XmlPeer objects,
     * or a TagMap object that reads a TagMap in XML.
     * Here we used a hybrid solution (for educational purposes only!)
     * with on one side the tags in the XML tagmap, on the other side
     * an XmlPeer object that overrides the properties of one of the tags.
     */

    class RomeoJulietMap extends TagMap {

            private static final long serialVersionUID = 1024517625414654121L;

            /**
             * Constructs a TagMap based on an XML file
             * and/or on XmlPeer objects that are added.
             * @throws IOException
             */
            public RomeoJulietMap() throws IOException {
                    super(new FileInputStream("tagmapRomeoJuliet.xml"));
                    XmlPeer peer = new XmlPeer(ElementTags.CHUNK, "SPEAKER");
                    peer.addValue(Markup.CSS_KEY_FONTSIZE, "10");
                    peer.addValue(Markup.CSS_KEY_FONTWEIGHT, Markup.CSS_VALUE_BOLD);
                    peer.addValue(ElementTags.GENERICTAG, "");
                    put(peer.getAlias(), peer);
            }
    }

    /**
     * This object contains a speaker and a number of occurrances in the play
     */

    class Speaker implements Comparable {

            // name of the speaker
            private String name;

            // number of occurrances
            private int occurrance = 1;

            /**
             * One of the speakers in the play.
             * @param name
             */
            public Speaker(String name) {
                    this.name = name;
            }

            /**
             * Gets the name of the speaker.
             * @return a name
             */
            public String getName() {
                    return name;
            }

            /**
             * Gets the number of occurances of the speaker.
             * @return a number of textblocks
             */
            public int getOccurrance() {
                    return occurrance;
            }

            /**
             * There is something odd going on in this compareTo.
             * Do you see it?
             * @param o an other speaker object
             * @see java.lang.Comparable#compareTo(java.lang.Object)
             */
            public int compareTo(Object o) {
                    Speaker otherSpeaker = (Speaker) o;
                    if (otherSpeaker.getName().equals(name)) {
                            this.occurrance += otherSpeaker.getOccurrance();
                            otherSpeaker.occurrance = this.occurrance;
                            return 0;
                    }
                    return name.compareTo(otherSpeaker.getName());
            }
    }
}